﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace ScreenBroadcast
{
    class Program
    {
        static void Main()
        {
            var cfgPath = GetConfigPath("server.cfg");
            if (!File.Exists(cfgPath)) CreateDefaultConfig(cfgPath);
            var cfg = new ServerConfig(cfgPath);
            var server = new HttpService(cfg.Port);
            Console.WriteLine(cfg);
            var handler = new ScreenBroadcastRequestHandler {FPS = cfg.FPS};
            server.Error += (s, e) => Console.WriteLine(e.Args.Message);
            server.Handlers.Add(handler);
            server.Start();
            Console.ReadLine();
            server.Stop();
            Thread.Sleep(100);
        }
        private static void CreateDefaultConfig(string path)
        {
            using (var w = new StreamWriter(path))
            {
                w.WriteLine("fps=8");
                w.WriteLine("port=8088");
            }
        }
        private static string GetConfigPath(string p)
        {
            var dir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) ?? ".\\";
            return Path.Combine(dir, p);
        }
    }
    public class ServerConfig
    {
        public int FPS { get; set; }
        public int Port { get; set; }
        public ServerConfig(int port, int fps)
        {
            FPS = fps;
            Port = port;
        }
        public ServerConfig(string path)
        {
            var lines = File.ReadAllLines(path);
            foreach (var line in lines)
            {
                var index = line.IndexOf('=');
                if (index < 1) continue;
                var key = line.Substring(0, index);
                var value = line.Substring(index + 1);
                switch (key.ToLower())
                {
                    case "fps":
                        int fps;
                        if (!int.TryParse(value, out fps)) continue;
                        FPS = fps;
                        break;
                    case "port":
                        int port;
                        if (!int.TryParse(value, out port)) continue;
                        Port = port;
                        break;
                }
            }
        }
        public override string ToString()
        {
            var sb = new StringBuilder();
            sb.AppendLine("[Server]");
            sb.AppendLine("Port: " + Port);
            sb.AppendLine("FPS: " + FPS);
            return sb.ToString();
        }
    }
    public class SocketClientEventArgs : EventArgs
    {
        public Socket Client { get; private set; }
        public SocketClientEventArgs(Socket client)
        {
            Client = client;
        }
    }
    public class ClientEventArgs : EventArgs
    {
        public Socket Client { get; private set; }
        public ClientEventArgs(Socket client)
        {
            Client = client;
        }
        }
    public class EventArgs<T> : EventArgs
    {
        public T Args { get; private set; }
        public EventArgs(T args)
        {
            Args = args;
        }
    }
    public class TcpServer
    {
        public event EventHandler<ClientEventArgs> ClientConnected;
        protected virtual void OnClientConnected(ClientEventArgs e)
        {
            if (ClientConnected != null) ClientConnected(this, e);
        }

        public event EventHandler<EventArgs<Exception>> Error;
        protected virtual void OnError(EventArgs<Exception> e)
        {
            if (Error != null) Error(this, e);
        }
        private readonly int _port;
        private Socket _socket;
        private readonly List<object> _sync;
        protected bool GetSync(string token) { lock (_sync) { return _sync.Contains(token); } }
        protected void SetSync(string token, bool enable)
        {
            lock (_sync)
            {
                var contain = _sync.Contains(token);
                if ((enable && contain) || (!enable && !contain)) return;
                if (contain)
                    _sync.Remove(token);
                else
                    _sync.Add(token);
            }

        }
        public bool IsRunning
        {
            get { return GetSync("IsRunning"); }
            private set { SetSync("IsRunning", value); }
        }
        protected bool KeepRunning
        {
            get { return GetSync("KeepRunning"); }
            set { SetSync("KeepRunning", value); }
        }
        public TcpServer(int port)
        {
            _sync = new List<object>();
            _port = port;
        }
        public void Start()
        {
            if (IsRunning) return;
            KeepRunning = true;
            IsRunning = true;
            new Thread(AcceptClient) { IsBackground = true }.Start();
        }
        protected virtual void AcceptClient()
        {
            while (KeepRunning)
            {
                try
                {
                    _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    _socket.Bind(new IPEndPoint(IPAddress.Any, _port));
                    _socket.Listen(8);
                    while (KeepRunning)
                    {
                        Socket client;
                        try { client = _socket.Accept(); }
                        catch (SocketException ex)
                        {
                            OnError(new EventArgs<Exception>(ex));
                            break;
                        }
                        try { OnClientConnected(new ClientEventArgs(client)); }
                        catch (Exception ex)
                        {
                            OnError(new EventArgs<Exception>(ex));
                        }
                    }
                }
                catch (Exception ex)
                {
                    OnError(new EventArgs<Exception>(ex));
                }
            }
            IsRunning = false;
            _socket.Close();
            _socket = null;
        }
        public void Stop()
        {
            if (!IsRunning) return;
            if (_socket == null) return;
            KeepRunning = false;
            _socket.Close(100);
        }
    }    
    public class ClientRequest
    {
        private readonly List<byte> _buffer;
        private Action<byte> _fp;
        public string Method { get; private set; }
        public Dictionary<string, string> QueryString { get; private set; }
        public string Protocol { get; private set; }
        public string Path { get; private set; }
        public string RawUrl { get; private set; }

        public Dictionary<string, string> Headers { get; private set; }
        public Socket Client { get; private set; }
        public ClientRequest(Socket client)
        {
            Client = client;
            _buffer = new List<byte>();
            _fp = ReadMethod;
            Headers = new Dictionary<string, string>();
            var buffer = new byte[1];
            var timeout = DateTime.Now.Add(TimeSpan.FromSeconds(10));
            while (true)
            {
                if (client.Available < 1)
                {
                    if (timeout >= DateTime.Now) continue;
                    break;
                }
                var len = client.Receive(buffer, 0, 1, SocketFlags.None);
                if (len < 1)
                {
                    if (timeout >= DateTime.Now) continue;
                    break;
                }
                timeout = DateTime.Now.Add(TimeSpan.FromSeconds(10));
                _fp(buffer[0]);
                if (_fp == EndOfHeader) break;
            }
        }
        private void ReadMethod(byte value)
        {
            if (value != ' ')
            {
                _buffer.Add(value);
                return;
            }
            Method = Encoding.Default.GetString(_buffer.ToArray());
            _buffer.Clear();
            _fp = ReadUrl;
        }
        private void ReadUrl(byte value)
        {
            if (value != ' ')
            {
                _buffer.Add(value);
                return;
            }
            RawUrl = Encoding.Default.GetString(_buffer.ToArray());
            var index = RawUrl.IndexOf('?');
            QueryString = new Dictionary<string, string>();
            if (index < 0)
            {
                Path = RawUrl;
            }
            else
            {
                Path = RawUrl.Substring(0, index);

                var list = RawUrl.Substring(index + 1).Split('&');
                foreach (var i in list)
                {
                    if (string.IsNullOrEmpty(i)) continue;
                    var e = i.IndexOf('=');
                    if (e < 1 || e == i.Length - 1) continue;
                    QueryString[i.Substring(0, e)] = i.Substring(e + 1);
                }
            }
            _buffer.Clear();
            _fp = ReadProtocol;
        }
        private void ReadProtocol(byte value)
        {
            if (value == '\r') return;
            if (value != '\n')
            {
                _buffer.Add(value);
                return;
            }
            Protocol = Encoding.Default.GetString(_buffer.ToArray());
            _buffer.Clear();
            _fp = ReadHeaders;
        }
        private static void EndOfHeader(byte value){}
        private void ReadHeaders(byte value)
        {
            if (value == '\r') return;
            if (value != '\n')
            {
                _buffer.Add(value);
                return;
            }
            if (_buffer.Count < 1)
            {
                _fp = EndOfHeader;
                return;
            }
            var item = Encoding.Default.GetString(_buffer.ToArray());
            _buffer.Clear();
            var index = item.IndexOf(": ");
            if (index > 0)
                Headers[item.Substring(0, index)] = item.Substring(index + 2);
            else
                Headers[item] = string.Empty;
        }

        public void ResponseStatus(HttpStatusCodes status)
        {
            switch (status)
            {
                case HttpStatusCodes.RedirectionMovedPermanently    : Client.Send(HttpStatusByteCode.RedirectionMovedPermanently    ); break;
                case HttpStatusCodes.RedirectionMovedTemporarily    : Client.Send(HttpStatusByteCode.RedirectionMovedTemporarily    ); break;
                case HttpStatusCodes.RedirectionSeeOther            : Client.Send(HttpStatusByteCode.RedirectionSeeOther            ); break;
                case HttpStatusCodes.RedirectionNotModified         : Client.Send(HttpStatusByteCode.RedirectionNotModified         ); break;
                case HttpStatusCodes.ClientErrorBadRequest          : Client.Send(HttpStatusByteCode.ClientErrorBadRequest          ); break;
                case HttpStatusCodes.ClientErrorUnauthorized        : Client.Send(HttpStatusByteCode.ClientErrorUnauthorized        ); break;
                case HttpStatusCodes.ClientErrorForbidden           : Client.Send(HttpStatusByteCode.ClientErrorForbidden           ); break;
                case HttpStatusCodes.ClientErrorNotFound            : Client.Send(HttpStatusByteCode.ClientErrorNotFound            ); break;
                case HttpStatusCodes.ServerErrorInternalServerError : Client.Send(HttpStatusByteCode.ServerErrorInternalServerError ); break;
                case HttpStatusCodes.ServerErrorNotImplemented      : Client.Send(HttpStatusByteCode.ServerErrorNotImplemented      ); break;
                case HttpStatusCodes.ServerErrorBadGateway          : Client.Send(HttpStatusByteCode.ServerErrorBadGateway          ); break;
                case HttpStatusCodes.ServerErrorServiceUnavailable  : Client.Send(HttpStatusByteCode.ServerErrorServiceUnavailable  ); break;
            }
        }
    }
    public enum HttpStatusCodes
    {
        RedirectionMovedPermanently,
        RedirectionMovedTemporarily,
        RedirectionSeeOther,
        RedirectionNotModified,
        ClientErrorBadRequest,
        ClientErrorUnauthorized,
        ClientErrorForbidden,
        ClientErrorNotFound,
        ServerErrorInternalServerError,
        ServerErrorNotImplemented,
        ServerErrorBadGateway,
        ServerErrorServiceUnavailable,

    }
    public static class HttpStatusByteCode
    {
        public static readonly byte[] RedirectionMovedPermanently              = Encoding.UTF8.GetBytes("HTTP/1.0 300 Moved Permanently    \r\nContent-Length: 0\r\nContent-Type: text/html\r\n\r\n");
        public static readonly byte[] RedirectionMovedTemporarily              = Encoding.UTF8.GetBytes("HTTP/1.0 301 Moved Temporarily    \r\nContent-Length: 0\r\nContent-Type: text/html\r\n\r\n");
        public static readonly byte[] RedirectionSeeOther                      = Encoding.UTF8.GetBytes("HTTP/1.0 302 See Other            \r\nContent-Length: 0\r\nContent-Type: text/html\r\n\r\n");
        public static readonly byte[] RedirectionNotModified                   = Encoding.UTF8.GetBytes("HTTP/1.0 304 Not Modified         \r\nContent-Length: 0\r\nContent-Type: text/html\r\n\r\n");
        public static readonly byte[] ClientErrorBadRequest                    = Encoding.UTF8.GetBytes("HTTP/1.0 400 Bad Request          \r\nContent-Length: 0\r\nContent-Type: text/html\r\n\r\n");
        public static readonly byte[] ClientErrorUnauthorized                  = Encoding.UTF8.GetBytes("HTTP/1.0 401 Unauthorized         \r\nContent-Length: 0\r\nContent-Type: text/html\r\n\r\n");
        public static readonly byte[] ClientErrorForbidden                     = Encoding.UTF8.GetBytes("HTTP/1.0 403 Forbidden            \r\nContent-Length: 0\r\nContent-Type: text/html\r\n\r\n");
        public static readonly byte[] ClientErrorNotFound                      = Encoding.UTF8.GetBytes("HTTP/1.0 404 Not Found            \r\nContent-Length: 0\r\nContent-Type: text/html\r\n\r\n");
        public static readonly byte[] ServerErrorInternalServerError           = Encoding.UTF8.GetBytes("HTTP/1.0 500 InternalServerError  \r\nContent-Length: 0\r\nContent-Type: text/html\r\n\r\n");
        public static readonly byte[] ServerErrorNotImplemented                = Encoding.UTF8.GetBytes("HTTP/1.0 501 NotImplemented       \r\nContent-Length: 0\r\nContent-Type: text/html\r\n\r\n");
        public static readonly byte[] ServerErrorBadGateway                    = Encoding.UTF8.GetBytes("HTTP/1.0 502 BadGateway           \r\nContent-Length: 0\r\nContent-Type: text/html\r\n\r\n");
        public static readonly byte[] ServerErrorServiceUnavailable            = Encoding.UTF8.GetBytes("HTTP/1.0 503 ServiceUnavailable   \r\nContent-Length: 0\r\nContent-Type: text/html\r\n\r\n");
    }
    public interface IRequestHandler
    {
        bool CanHandle(ClientRequest request);
        bool IsRunning { get; }
        void Process(ClientRequest request);
        void Start();
        void Stop();
    }
    public class HttpService
    {
        public event EventHandler<EventArgs<Exception>> Error;
        protected virtual void OnError(EventArgs<Exception> e)
        {
            if (Error != null) Error(this, e);
        }

        private readonly TcpServer _tcp;
        public readonly List<IRequestHandler> Handlers;
        public HttpService(int port)
        {
            Handlers = new List<IRequestHandler>();
            _tcp = new TcpServer(port);
            _tcp.ClientConnected += ClientConnected;
            _tcp.Error += (s, e) => OnError(e);
        }

        private void ClientConnected(object sender, ClientEventArgs e)
        {
            var request = new ClientRequest(e.Client);
            foreach (var handler in Handlers)
            {
                if (!handler.CanHandle(request)) continue;
                handler.Process(request);
                Console.WriteLine("{0} {1}", e.Client.RemoteEndPoint, request.RawUrl);
                return;
            }
            Console.WriteLine("{0} {1} (404 NOT FOUND)", e.Client.RemoteEndPoint, request.RawUrl);
            request.ResponseStatus(HttpStatusCodes.ClientErrorNotFound);
        }

        public void Start()
        {
            _tcp.Start();
            foreach (var handler in Handlers)
            {
                handler.Start();
            }
        }

        public void Stop()
        {
            _tcp.Stop();
            foreach (var handler in Handlers)
            {
                handler.Stop();
            }
        }
    }
    public class ImageResizer : IDisposable
    {
        private readonly MemoryStream _mem;
        private readonly Graphics _g;
        private readonly Bitmap _bmp;
        private readonly Rectangle _rect;

        public ImageResizer(int width, int height, Graphics source)
        {
            _rect = new Rectangle(0, 0, width, height);
            _bmp = new Bitmap(width, height, source);
            _g = Graphics.FromImage(_bmp);
            _mem = new MemoryStream();
        }
        ~ImageResizer()
        {
            Dispose();
        }
        public byte[] GetBuffer(Image image)
        {
            return GetBuffer(image, ImageFormat.Jpeg);
        }
        public byte[] GetBuffer(Image image, ImageFormat format)
        {
            return GetBuffer(image, format, null);
        }
        public byte[] GetBuffer(Image image, ImageFormat format, PaintEventHandler paintCallback)
        {
            _mem.SetLength(0);
            _g.DrawImage(image, 0, 0, _bmp.Width, _bmp.Height);
            if (paintCallback != null) paintCallback(_bmp, new PaintEventArgs(_g, _rect));
            _bmp.Save(_mem, format);
            return _mem.ToArray();
        }
        public void Dispose()
        {
            _mem.Dispose();
            _g.Dispose();
            _bmp.Dispose();
        }
    }
    public interface IPainter
    {
        bool Enabled { get; set; }
        void Paint(Graphics g);
    }

    public class ScreenBroadcastRequestHandler : IRequestHandler
    {
        private readonly List<ClientRequest> _clients;
        private readonly List<object> _sync;
        private bool GetSync(object token)
        {
            lock (_sync)
            {
                return _sync.Contains(token);
            }
        }
        private void SetSync(object token, bool enable)
        {
            lock (_sync)
            {
                var contains = _sync.Contains(token);
                if ((enable && contains) || (!enable && !contains)) return;
                if (enable)
                    _sync.Add(token);
                else
                    _sync.Remove(token);
            }
        }
        public ScreenBroadcastRequestHandler()
        {
            _clients = new List<ClientRequest>();
            _sync = new List<object>();
            FPS = 8;
        }

        public bool CanHandle(ClientRequest request)
        {
            return true;
        }

        public bool IsRunning
        {
            get { return GetSync("IsRunning"); }
            private set { SetSync("IsRunning", value); }
        }
        protected bool KeepRunning
        {
            get { return GetSync("KeepRunning"); }
            set { SetSync("KeepRunning", value); }
        }

        public int FPS { get; set; }
        public void Process(ClientRequest request)
        {
            if (request == null) return;
            try
            {
                if (request.RawUrl == null || request.RawUrl == "/")
                {
                    if (!Directory.Exists("html") || !File.Exists(@"html\home.htm"))
                    {
                        request.Client.Send(_error);
                        return;
                    }
                    request.Client.Send(_htmlRequestHeader);
                    request.Client.Send(File.ReadAllBytes(@"html\home.htm"));
                    return;
                }
                if (request.RawUrl.ToLower() == "/live")
                {
                    request.Client.Blocking = true;
                    request.Client.Send(_imageRequestHeader);
                    _clients.Add(request);
                    return;
                }
                var q = request.RawUrl.IndexOf('?');
                var path = "html"+((q < 0) ? request.RawUrl : request.RawUrl.Substring(0, q)).ToLower();
                if (!File.Exists(path))
                {
                    request.Client.Send(_error);
                    return;
                }
                request.Client.Send(_htmlRequestHeader);
                request.Client.Send(File.ReadAllBytes(path));
                return;
                //return request.RawUrl.ToLower().EndsWith("live");

                
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
        public void Start()
        {
            if (IsRunning) return;
            IsRunning = true;
            new Thread(CaptureScreen) { IsBackground = true }.Start();
        }

        private readonly byte[] _htmlRequestHeader =
            Encoding.UTF8.GetBytes(@"HTTP/1.0 200 OK
Content-Type: text/html

");
        private readonly byte[] _imageRequestHeader =
            Encoding.UTF8.GetBytes(
                @"HTTP/1.0 200 OK
Content-Type: multipart/x-mixed-replace; boundary=2CC4C358-90C3-4472-9882-6A8B25EF4CA4

--2CC4C358-90C3-4472-9882-6A8B25EF4CA4
");
        private readonly byte[] _imageHeader = Encoding.UTF8.GetBytes(@"Content-Type: image/jpeg

");
        private readonly byte[] _imageFooter = Encoding.UTF8.GetBytes(@"
--2CC4C358-90C3-4472-9882-6A8B25EF4CA4
");
        private readonly byte[] _error = Encoding.UTF8.GetBytes(@"HTTP/1.0 404 Not Found
Content-Length: 0
Content-Type: text/html

");

        [DllImport("user32.dll")]
        private static extern IntPtr GetDesktopWindow();
        [DllImport("winmm.dll")]
        internal static extern uint timeBeginPeriod(uint period);
        [DllImport("winmm.dll")]
        internal static extern uint timeEndPeriod(uint period);


        private void CaptureScreen()
        {
            KeepRunning = true;
            var desktopGraphics = Graphics.FromHwnd(GetDesktopWindow());

            var bounds = Screen.PrimaryScreen.Bounds;
            var bmp = new Bitmap(bounds.Width, bounds.Height, desktopGraphics);
            var g = Graphics.FromImage(bmp);
            var interval = TimeSpan.FromSeconds(1.0/FPS);

            //var resizer = new ImageResizer(320, 240, g);
            var resizer = new ImageResizer(bounds.Width/2, bounds.Height/2, g);
            try
            {
                var timer = new Stopwatch();
                var font = new Font("Arial", 24);
                var list = new List<ClientRequest>();
                var fps = 30.0;
                var fpsText = "FPS: 30.00";
                PaintEventHandler paint =
                    (s, e) =>
                        {
                            e.Graphics.DrawString(fpsText, font, Brushes.White, 9, 9);
                            e.Graphics.DrawString(fpsText, font, Brushes.White, 9, 9);
                            e.Graphics.DrawString(fpsText, font, Brushes.White, 11, 9);
                            e.Graphics.DrawString(fpsText, font, Brushes.White, 11, 11);
                            e.Graphics.DrawString(fpsText, font, Brushes.White, 9, 11);
                            e.Graphics.DrawString(fpsText, font, Brushes.Black, 10, 10);
                        };
                //timeBeginPeriod(1);
                while (KeepRunning)
                {
                    try
                    {
                        timer.Start();
                        g.CopyFromScreen(0, 0, 0, 0, bounds.Size);
                        fpsText = string.Format("FPS: {0:0.00}", fps);
                        var buffer = resizer.GetBuffer(bmp, ImageFormat.Jpeg, paint);

                        foreach (var i in _clients.ToArray())
                        {
                            try
                            {
                                i.Client.Send(_imageHeader);
                                i.Client.Send(buffer);
                                i.Client.Send(_imageFooter);
                            }
                            catch (Exception ex)
                            {
                                list.Add(i);
                            }
                        }
                        foreach (var i in list)
                            _clients.Remove(i);
                        list.Clear();

                        var sleep = interval - timer.Elapsed;
                        if (sleep > TimeSpan.Zero) Thread.Sleep(sleep);
                        timer.Stop();
                        fps = 1000.0/timer.ElapsedMilliseconds;
                        timer.Reset();
                        Debug.WriteLine("FPS: "+ fps + " Sleep: "+sleep);
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine(ex.Message);
                    }
                }
            }
            finally
            {
                //timeEndPeriod(1);
                desktopGraphics.Dispose();
                g.Dispose();
                bmp.Dispose();
                resizer.Dispose();
            }
            IsRunning = false;
        }
        public void Stop()
        {
            if (!IsRunning) return;
            KeepRunning = false;
        }
    }

}
